package com.intermancer.predictor.store.simple;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import com.intermancer.predictor.data.BaseFileDAO;
import com.intermancer.predictor.store.StoreEntry;
import com.intermancer.predictor.store.StoreEntryDAO;

public class StoreEntryFileDAO extends BaseFileDAO<StoreEntry> implements
		StoreEntryDAO {

	private class StoreEntryIndexRecord {
		Long identity;
		double score;
		int index;

		public StoreEntryIndexRecord(Long identity, double score, int index) {
			this.identity = identity;
			this.score = score;
			this.index = index;
		}

		@Override
		public boolean equals(Object obj) {
			StoreEntryIndexRecord other = (StoreEntryIndexRecord) obj;
			return this.identity.equals(other.identity);
		}

		@Override
		public int hashCode() {
			return identity.intValue();
		}

	}

	private class ScoreComparator implements Comparator<StoreEntryIndexRecord> {

		@Override
		public int compare(StoreEntryIndexRecord o1, StoreEntryIndexRecord o2) {
			return (int) (o1.score - o2.score);
		}

	}

	private String filePrefix;
	public static final String FILE_SUFFIX = ".xml";
	private static final long MAX_NOT_SET = -1L;
	private List<Long> identities;
	private List<StoreEntryIndexRecord> scoreIndex;
	private long currentMax = MAX_NOT_SET;

	private Comparator<StoreEntryIndexRecord> scoreComparator = new ScoreComparator();

	public StoreEntryFileDAO() {
		clazz = StoreEntry.class;
	}

	public StoreEntryFileDAO(String homeDirectory) {
		this();
		setHomeDirectory(homeDirectory);
	}

	@Override
	public void delete(Long identity) {
		deleteFile(getFullFilePath(identity));
		if (identities != null) {
			identities.remove(identity);
			StoreEntryIndexRecord searchRecord = new StoreEntryIndexRecord(
					identity, 0.0, 0);
			int index = scoreIndex.indexOf(searchRecord);
			scoreIndex.remove(index);
			reindex();
		}
	}

	private void reindex() {
		Collections.sort(scoreIndex, scoreComparator);
		int i = 0;
		for (StoreEntryIndexRecord record : scoreIndex) {
			record.index = i++;
		}
	}

	@Override
	public StoreEntry get(Long identity) {
		StoreEntry retreivedStoreEntry = (StoreEntry) getObject(getFullFilePath(identity));
		return retreivedStoreEntry;
	}

	@Override
	public StoreEntry put(StoreEntry storeEntry) {
		Long newIdentity = getNextIdentity();
		String filename = getFullFilePath(newIdentity);
		storeEntry.setIdentity(newIdentity);
		storeEntry.getOrganism().init();
		putObject(filename, storeEntry);
		if (identities != null) {
			identities.add(newIdentity);
			StoreEntryIndexRecord indexRecord = new StoreEntryIndexRecord(
					newIdentity, storeEntry.getScore(), scoreIndex.size());
			scoreIndex.add(indexRecord);
			reindex();
		}
		return storeEntry;
	}

	private synchronized Long getNextIdentity() {
		if (currentMax == MAX_NOT_SET) {
			getIdentities();
			currentMax = identities.size() > 0 ? identities.get(
					identities.size() - 1).longValue() : -1l;
		}
		currentMax++;
		return currentMax;
	}

	@Override
	public List<Long> getIdentities() {
		if (identities == null) {
			File homeDirectory = new File(this.homeDirectory);
			identities = new ArrayList<Long>();
			scoreIndex = new ArrayList<StoreEntryIndexRecord>();
			StringBuilder patternBuilder = new StringBuilder();
			if (StringUtils.isNotBlank(getFilePrefix())) {
				patternBuilder.append(getFilePrefix());
			}
			patternBuilder.append("(\\d*)");
			patternBuilder.append(FILE_SUFFIX);
			Pattern pattern = Pattern.compile(patternBuilder.toString());
			if ((homeDirectory.isDirectory()) && (homeDirectory.list() != null)) {
				for (String filename : homeDirectory.list()) {
					Matcher matcher = pattern.matcher(filename);
					if (matcher.matches()) {
						Long identity = new Long(matcher.group(1));
						identities.add(identity);
						StoreEntry entry = get(identity);
						StoreEntryIndexRecord indexEntry = new StoreEntryIndexRecord(
								identity, entry.getScore(), 0);
						scoreIndex.add(indexEntry);
					}
				}
			}
			reindex();
		}
		return identities;
	}

	@Override
	public List<StoreEntry> get(List<Long> identities) {
		List<StoreEntry> entries = new ArrayList<StoreEntry>(identities.size());
		for (Long id : identities) {
			StoreEntry storeEntry = (StoreEntry) getObject(getFullFilePath(id));
			entries.add(storeEntry);
		}
		return entries;
	}

	private String getFullFilePath(Long identity) {
		StringBuilder filename = new StringBuilder();
		filename.append(homeDirectory);
		filename.append(getFilename(identity));
		return filename.toString();
	}

	private String getFilename(Long identity) {
		StringBuilder filename = new StringBuilder();
		if (StringUtils.isNotBlank(getFilePrefix())) {
			filename.append(getFilePrefix());
		}
		filename.append(identity);
		filename.append(FILE_SUFFIX);
		return filename.toString();
	}

	public String getFilePrefix() {
		return filePrefix;
	}

	public void setFilePrefix(String filePrefix) {
		this.filePrefix = filePrefix;
	}

	@Override
	public int getSize() {
		getIdentities();
		return identities.size();
	}

	@Override
	public StoreEntry getByScoreIndex(int index) {
		getIdentities();
		return get(scoreIndex.get(index).identity);
	}

	@Override
	public int getScoreIndex(double score) {
		for (StoreEntryIndexRecord record : scoreIndex) {
			if (record.score > score) {
				return record.index;
			}
		}
		return scoreIndex.size();
	}

	@Override
	public int getMaxSize() {
		return NO_MAXIMUM_SIZE;
	}

}
